/*
 * Record.cpp
 *
 * This source file implements the operations declared 
 * at the header file: Record.h
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <iostream>

#include "g_crossbit/Record.h"

using namespace std;
using namespace g_crossbit;

//#define DEBUG

/* Initialize the static members of Record.*/
Record *Record::record_ptr = NULL;
int Record::record_num = 0;
int Record::record_in  = 0;

/*
 * Default constructor
 */
Record::Record (){}

/*
 * Specified constructor
 *
 * @dir: flow direction of data that will be copied.
 * @s_a: start address of data or register's number.
 * @lab: label identifing data's location in gpu's memory.
 * @e_a: ending address of data.
 * @d_t: data's type.
 */
Record::Record (short dir, unsigned int s_a, int s_o, 
		        const char *lab, int len, short d_t)
{
   direction  = dir;
   start_addr = s_a;
   start_off  = s_o;
   length     = len;
   data_type  = d_t;
   strcpy (label, lab);
}

short
Record::getDirection ()
{
   return direction;
}

unsigned int 
Record::getStartAddr ()
{
   return start_addr;
}

int
Record::getStartOffset ()
{
	return start_off;
}

const char *
Record::getLabel ()
{
   return label;
}

int 
Record::getLength ()
{
   return length;
}

short
Record::getDataType ()
{
   return data_type;
}

/*
 * @path_name:     an absolute or relative path of a file.
 * @process_state: current processor's state.
 *
 * This function will use the fstat function declared
 * at sys/stat.h to determine the number of records stored
 * at the specified file and allocate memory for those
 * records. After that, we read the contents of the file
 * into memory pointed by record_ptr and then make some 
 * substitutions. 
 */
void
Record::loadRecords (const char *path_name, IA32ProcessorState *cur_state)
{
   struct stat file_info;
   
   /*
    * As the file whose absolute or relative path is 
    * path_name certainly exist, so each operation 
    * on that file will be successful and we need
    * not to care about the return value of a 
    * specified operation. 
    */
   int ret = stat (path_name, &file_info);

#ifdef DEBUG
   printf ("path_name is %s\n", path_name);
   if (ret < 0)
	  printf ("stat error: %s", strerror (errno));
#endif

   record_num = (short)(file_info.st_size / sizeof (Record));
	
#ifdef DEBUG
   printf ("record_num = %d\n", record_num);
#endif

   /* Allocate memory for records */
   if ( (record_ptr = new Record[record_num]) == NULL) {
   	  cout << "There is not enough memory.\n";
	  exit (0);
   }

   int fd = open (path_name, O_RDONLY);
   read (fd, (void *)record_ptr, (size_t)file_info.st_size);
   close (fd);

#ifdef DEBUG
//   printf ("Original:\n");
//   dumpRecords ();
#endif
   
   /*
    * In the record, start_addr member stores start address
    * of data or register's number in which stores start
    * address of data. Therefore, if start_addr stores a 
    * register's number, we need to subsitute it's value 
    * with the real start address.
    */
   unsigned int start_addr;
   int i, data_type, start_off;
   int height_a, width_a, width_b;

   for (i = 0; i < record_num; i++) {
	  data_type = record_ptr[i].data_type;
   	  start_addr = cur_state->reg (record_ptr[i].start_addr);
	  
	  if ((data_type / 4 == 1) && (data_type % 4 == 0)) {
	  	 width_a = *((int *)(start_addr + record_ptr[i].start_off));
	  	 width_b = *((int *)(start_addr + record_ptr[i + 1].start_off));
	  	 height_a = *((int *)(start_addr + record_ptr[i + 2].start_off));
#ifdef DEBUG
		 printf ("height_a = %d, width_a = %d, width_b = %d\n",
				  height_a, width_a, width_b);
#endif
		 i += 3;
		 continue;
	  } 
	  /*
	  if (data_type / 4 == 2) {
	     if (data_type % 4 == 0)
			record_ptr[i].length = height_a * width_a * 4;
		 else if (data_type % 4 == 1)
			record_ptr[i].length = width_a * width_b * 4;
		 else if (data_type % 4 == 2)
			record_ptr[i].length = height_a * width_a * 4;
	  }
		 */
	  if (data_type / 4 == 2) {
	     if (data_type % 4 == 0)
			record_ptr[i].length = height_a * width_a * 8;
		 else if (data_type % 4 == 1)
			record_ptr[i].length = width_a * width_b * 8;
		 else if (data_type % 4 == 2)
			record_ptr[i].length = height_a * width_a * 8;
	  }
   }
//#ifdef DEBUG
//   printf ("After change length\n");
//   dumpRecords ();
//#endif
	  
   for (int i = 0; i < record_num; i++) {
   	  start_addr = record_ptr[i].start_addr;
      if (start_addr < 8 ) {
   	     start_addr = cur_state->reg (record_ptr[i].start_addr);
		 start_off  = record_ptr[i].start_off;
	  	 if (record_ptr[i].length <= 8)
			record_ptr[i].start_addr = start_addr + start_off;
		 else
		 record_ptr[i].start_addr = *((int *)(start_addr + start_off));
	  }
      if (record_ptr[i].direction == 0) 
         record_in++;
   }

#ifdef DEBUG
   printf ("After change Addr\n");
   dumpRecords ();
#endif
}

/*
 * Display records into screen.
 */
void
Record::dumpRecords ()
{
   printf ("\ndirection  start_addr start_off label      length     data_type\n\n");

   for (int i = 0; i < record_num; i++) {
   	  printf ("%-11d", record_ptr[i].direction);	 
   	  printf ("0x%08x ", record_ptr[i].start_addr);	 
   	  printf ("%-11d", record_ptr[i].start_off );	 
   	  printf ("%-11s", record_ptr[i].label     );	 
   	  printf ("%-11d", record_ptr[i].length    );	 
   	  printf ("%-11d", record_ptr[i].data_type );
	  printf ("\n");
   }

   printf ("\n\n");

   /*unsigned int start = record_ptr[5].start_addr;
   printf ("Width = %d\n\n", *((int *)start));
   
   printf ("five elements of MatrixA: \n");
   start = record_ptr[3].start_addr;
   for (int i = 0; i < 5; i++)
	  printf("%f ", *((float *)(start) + i)); 
   
   printf ("\nfive elements of MatrixB: \n");
   start = record_ptr[4].start_addr;
   for (int i = 0; i < 5; i++)
	  printf("%f ", *((float *)(start) + i)); 
   
   printf ("\nfive elements of MatrixA: \n");
   start = record_ptr[7].start_addr;
   for (int i = 0; i < 5; i++)
	  printf("%f ", *((float *)(start) + i)); 
   printf ("\n"); */
}

/*
 * Get a record through a specified address.
 *
 * Parameters
 * @addr: a specified address.
 * @dir : flow direction of data.
 */
Record *
Record::getRecord (unsigned int addr, short dir)
{
   int length;
   unsigned int start_addr;

   int index = (dir == 0) ? 0 : record_in;
   int end_cond = (dir == 0) ? record_in : record_num;
   for (; index < end_cond; index++) {
      start_addr = record_ptr[index].getStartAddr ();
      length     = record_ptr[index].getLength ();
      if (addr >= start_addr && addr <= start_addr + length)
         return (record_ptr + index);
   }
}

/*
 * Free memory allocated in loadRecords.
 *
 * Parameters: null
 */
void 
Record::freeRecords ()
{
   if (record_ptr != NULL)
      delete record_ptr;
}
